package main

import (
	"context"
	"fmt"
	v3 "github.com/coreos/etcd/clientv3"
	"strconv"
	"time"
)

func txnXfer(etcd *v3.Client, accountA, accountB string, amount int) error {
	// 失败重试
	for {
		if ok, err := doTxnXfer(etcd, accountA, accountB, amount); err != nil {
			fmt.Println("转账失败：", err)
			return err
		} else if ok {
			fmt.Println("转账成功")
			return nil
		}
	}
}

func toInt(buf []byte) int {
	v := string(buf)
	vv, _ := strconv.Atoi(v)
	return vv
}

func doTxnXfer(cli *v3.Client, accountA, accountB string, amount int) (bool, error) {
	// 获取from，to账户金额
	ctx, cancel := context.WithTimeout(context.Background(), time.Second)
	getresp, err := cli.Txn(ctx).Then(v3.OpGet(accountA), v3.OpGet(accountB)).Commit()
	if err != nil {
		return false, err
	}
	fromKV := getresp.Responses[0].GetResponseRange().Kvs[0]
	toKV := getresp.Responses[1].GetResponseRange().Kvs[0]
	fromV, toV := toInt(fromKV.Value), toInt(toKV.Value)
	// 验证账户余额是否充足
	if fromV < amount {
		return false, fmt.Errorf("insufficient value")
	}
	// 发起转账视图
	txn := cli.Txn(ctx).If(
		// 事务提交时，from账户余额没有没有变动
		v3.Compare(v3.ModRevision(accountA), "=", fromKV.ModRevision),
		// 事务提交时，to账户余额没有变动
		v3.Compare(v3.ModRevision(accountB), "=", toKV.ModRevision))
	txn = txn.Then(
		v3.OpPut(accountA, strconv.Itoa(fromV-amount)), // 更新from账户余额
		v3.OpPut(accountB, strconv.Itoa(toV+amount)),   // 更新to账户余额
	)
	putresp, err := txn.Commit() // 提交事务
	if err != nil {
		return false, err
	}
	cancel()
	return putresp.Succeeded, nil //如果putresp.Succeeded为true，则表示If条件为true，成功执行Then里的转账逻辑
}

func main() {
	cli, err := v3.New(v3.Config{Endpoints: []string{"localhost:2379"},
		DialTimeout: 5 * time.Second,
	})
	if err != nil {
		fmt.Println(err)
	}
	status, err := doTxnXfer(cli, "/user/zhanglin", "/user/liuwei", 1)
	fmt.Println(111, status, err)
}
